﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace GenericStateMachine.Test
{
    public class TestService
    {
        #region state machine

        #region interfaces

        internal enum ServiceState
        {
            [State(typeof(ServiceStateCreated), StateGroup.Initial)]
            Created,

            [State(typeof(ServiceStateStopped))]
            Stopped,
        }

        internal interface IServiceStateContext : IStateContext<IServiceStateContext, IServiceState, ServiceState>
        {
            // Context --> State

            void Action0();
            void Action1(byte param1);
            void Action2(byte param1, short param2);
            void Action3(byte param1, short param2, int param3);
            void Action4(byte param1, short param2, int param3, long param4);

            string Func0();
            string Func1(long param1);
            string Func2(long param1, int param2);
            string Func3(long param1, int param2, short param3);
            string Func4(long param1, int param2, short param3, byte param4);

            void Stop();


            // State --> Context

            string UpdateParameter(string methodName, object param);

            void StopCore();
        }

        internal interface IServiceState : IState<IServiceStateContext, IServiceState, ServiceState>
        {
            ServiceState Action0();
            ServiceState Action1(byte param1);
            ServiceState Action2(byte param1, short param2);
            ServiceState Action3(byte param1, short param2, int param3);
            ServiceState Action4(byte param1, short param2, int param3, long param4);

            Tuple<ServiceState, string> Func0();
            Tuple<ServiceState, string> Func1(long param1);
            Tuple<ServiceState, string> Func2(long param1, int param2);
            Tuple<ServiceState, string> Func3(long param1, int param2, short param3);
            Tuple<ServiceState, string> Func4(long param1, int param2, short param3, byte param4);

            ServiceState Stop();
        }

        #endregion

        #region context class

        internal class ServiceStateContext : StateContextBase<IServiceStateContext, IServiceState, ServiceState>,
            IServiceStateContext
        {
            private readonly TestService parent;

            public ServiceStateContext(TestService parent)
            {
                this.parent = parent;
            }

            public void Action0()
            {
                var action = GetHandlerAction(s => s.Action0());
                action();
            }

            public void Action1(byte param1)
            {
                var action = GetHandlerAction<byte>((s, p1) => s.Action1(p1));
                action(param1);
            }

            public void Action2(byte param1, short param2)
            {
                var action = GetHandlerAction<byte, short>((s, p1, p2) => s.Action2(p1, p2));
                action(param1, param2);
            }

            public void Action3(byte param1, short param2, int param3)
            {
                var action = GetHandlerAction<byte, short, int>((s, p1, p2, p3) => s.Action3(p1, p2, p3));
                action(param1, param2, param3);
            }

            public void Action4(byte param1, short param2, int param3, long param4)
            {
                var action = GetHandlerAction<byte, short, int, long>((s, p1, p2, p3, p4) => s.Action4(p1, p2, p3, p4));
                action(param1, param2, param3, param4);
            }

            public string Func0()
            {
                var func = GetHandlerFunc<string>(s => s.Func0());
                var ret = func();
                return ret;
            }

            public string Func1(long param1)
            {
                var func = GetHandlerFunc<long, string>((s, p1) => s.Func1(p1));
                var ret = func(param1);
                return ret;
            }

            public string Func2(long param1, int param2)
            {
                var func = GetHandlerFunc<long, int, string>((s, p1, p2) => s.Func2(p1, p2));
                var ret = func(param1, param2);
                return ret;
            }

            public string Func3(long param1, int param2, short param3)
            {
                var func = GetHandlerFunc<long, int, short, string>((s, p1, p2, p3) => s.Func3(p1, p2, p3));
                var ret = func(param1, param2, param3);
                return ret;
            }

            public string Func4(long param1, int param2, short param3, byte param4)
            {
                var func = GetHandlerFunc<long, int, short, byte, string>((s, p1, p2, p3, p4) => s.Func4(p1, p2, p3, p4));
                var ret = func(param1, param2, param3, param4);
                return ret;
            }

            public void Stop()
            {
                var action = GetHandlerAction(s => s.Stop());
                action();
            }

            public string UpdateParameter(string methodName, object param)
            {
                Debug.WriteLine("ServiceStateContext.UpdateParameter");

                var ret = parent.UpdateParameter(methodName, param);
                return ret;
            }

            public void StopCore()
            {
                Debug.WriteLine("ServiceStateContext.StopCore");
                parent.StopCore();
            }

            protected override void OnStateChanging(IServiceState oldState, IServiceState newState, string eventName)
            {
                base.OnStateChanging(oldState, newState, eventName);

                Debug.WriteLine("ServiceStateContext.OnStateChanging {0} -> {1} ({2})", oldState.StateType, newState.StateType, eventName);
            }

            protected override void OnStateChanged(IServiceState oldState, IServiceState newState, string eventName)
            {
                base.OnStateChanged(oldState, newState, eventName);

                Debug.WriteLine("ServiceStateContext.OnStateChanged {0} -> {1} ({2})", oldState.StateType, newState.StateType, eventName);
            }
        }

        #endregion

        #region state classes

        internal abstract class ServiceStateBase : StateBase<IServiceStateContext, IServiceState, ServiceState>,
            IServiceState
        {
            public override ServiceState OnEntry()
            {
                var ret = base.OnEntry();

                Debug.WriteLine("{0}.{1}", GetType().Name, "OnEntry");

                return ret;
            }

            public override void OnExit()
            {
                base.OnExit();

                Debug.WriteLine("{0}.{1}", GetType().Name, "OnExit");
            }

            public virtual ServiceState Action0()
            {
                return ThrowInvalidStateException(() => Action0());
            }

            public virtual ServiceState Action1(byte param1)
            {
                return ThrowInvalidStateException(() => Action1(param1));
            }

            public virtual ServiceState Action2(byte param1, short param2)
            {
                return ThrowInvalidStateException(() => Action2(param1, param2));
            }

            public virtual ServiceState Action3(byte param1, short param2, int param3)
            {
                return ThrowInvalidStateException(() => Action3(param1, param1, param3));
            }

            public virtual ServiceState Action4(byte param1, short param2, int param3, long param4)
            {
                return ThrowInvalidStateException(() => Action4(param1, param2, param3, param4));
            }

            public virtual Tuple<ServiceState, string> Func0()
            {
                return ThrowInvalidStateException(() => Func0());
            }

            public virtual Tuple<ServiceState, string> Func1(long param1)
            {
                return ThrowInvalidStateException(() => Func1(param1));
            }

            public virtual Tuple<ServiceState, string> Func2(long param1, int param2)
            {
                return ThrowInvalidStateException(() => Func2(param1, param2));
            }

            public virtual Tuple<ServiceState, string> Func3(long param1, int param2, short param3)
            {
                return ThrowInvalidStateException(() => Func3(param1, param2, param3));
            }

            public virtual Tuple<ServiceState, string> Func4(long param1, int param2, short param3, byte param4)
            {
                return ThrowInvalidStateException(() => Func4(param1, param2, param3, param4));
            }

            public virtual ServiceState Stop()
            {
                return ThrowInvalidStateException(() => Stop());
            }
        }

        [StateClassFor(ServiceState.Created)]
        internal class ServiceStateCreated : ServiceStateBase
        {
            public override ServiceState Action0()
            {
                Context.UpdateParameter("Action0", Unit.Default);
                var ret = StateType;
                return ret;
            }

            public override ServiceState Action1(byte param1)
            {
                Context.UpdateParameter("Action1", Tuple.Create(param1));
                var ret = StateType;
                return ret;
            }

            public override ServiceState Action2(byte param1, short param2)
            {
                Context.UpdateParameter("Action2", Tuple.Create(param1, param2));
                var ret = StateType;
                return ret;
            }

            public override ServiceState Action3(byte param1, short param2, int param3)
            {
                Context.UpdateParameter("Action3", Tuple.Create(param1, param2, param3));
                var ret = StateType;
                return ret;
            }

            public override ServiceState Action4(byte param1, short param2, int param3, long param4)
            {
                Context.UpdateParameter("Action4", Tuple.Create(param1, param2, param3, param4));
                var ret = StateType;
                return ret;
            }

            public override Tuple<ServiceState, string> Func0()
            {
                var result = Context.UpdateParameter("Func0", Unit.Default);
                var ret = Tuple.Create(StateType, result);
                return ret;
            }

            public override Tuple<ServiceState, string> Func1(long param1)
            {
                var result = Context.UpdateParameter("Func1", Tuple.Create(param1));
                var ret = Tuple.Create(StateType, result);
                return ret;
            }

            public override Tuple<ServiceState, string> Func2(long param1, int param2)
            {
                var result = Context.UpdateParameter("Func2", Tuple.Create(param1, param2));
                var ret = Tuple.Create(StateType, result);
                return ret;
            }

            public override Tuple<ServiceState, string> Func3(long param1, int param2, short param3)
            {
                var result = Context.UpdateParameter("Func3", Tuple.Create(param1, param2, param3));
                var ret = Tuple.Create(StateType, result);
                return ret;
            }

            public override Tuple<ServiceState, string> Func4(long param1, int param2, short param3, byte param4)
            {
                var result = Context.UpdateParameter("Func4", Tuple.Create(param1, param2, param3, param4));
                var ret = Tuple.Create(StateType, result);
                return ret;
            }

            public override ServiceState Stop()
            {
                var ret = ServiceState.Stopped;
                return ret;
            }
        }

        [StateClassFor(ServiceState.Stopped)]
        internal class ServiceStateStopped : ServiceStateBase
        {
            public override ServiceState OnEntry()
            {
                var ret = base.OnEntry();

                Context.StopCore();

                return ret;
            }

            public override ServiceState Stop()
            {
                // NOP
                var ret = StateType;
                return ret;
            }
        }

        #endregion

        #endregion

        #region private fields

        private readonly ServiceStateContext stateMachine;

        private object parameter;

        #endregion

        #region constructor

        public TestService()
        {
            stateMachine = new ServiceStateContext(this);
            parameter = null;
        }

        #endregion

        public object Parameter { get { return parameter; } }

        #region public methods

        public void Action0()
        {
            stateMachine.Action0();
        }

        public void Action1(byte param1)
        {
            stateMachine.Action1(param1);
        }

        public void Action2(byte param1, short param2)
        {
            stateMachine.Action2(param1, param2);
        }

        public void Action3(byte param1, short param2, int param3)
        {
            stateMachine.Action3(param1, param2, param3);
        }

        public void Action4(byte param1, short param2, int param3, long param4)
        {
            stateMachine.Action4(param1, param2, param3, param4);
        }

        public string Func0()
        {
            var ret = stateMachine.Func0();
            return ret;
        }

        public string Func1(long param1)
        {
            var ret = stateMachine.Func1(param1);
            return ret;
        }

        public string Func2(long param1, int param2)
        {
            var ret = stateMachine.Func2(param1, param2);
            return ret;
        }

        public string Func3(long param1, int param2, short param3)
        {
            var ret = stateMachine.Func3(param1, param2, param3);
            return ret;
        }

        public string Func4(long param1, int param2, short param3, byte param4)
        {
            var ret = stateMachine.Func4(param1, param2, param3, param4);
            return ret;
        }

        public void Stop()
        {
            stateMachine.Stop();
        }

        #endregion

        #region private methods

        private string UpdateParameter(string methodName, object param)
        {
            parameter = param;
            var ret = methodName;

            Debug.WriteLine("TestService.UpdateParameter: {0}({1})", methodName, param);
            return ret;
        }

        private void StopCore()
        {
            Debug.WriteLine("TestService.StopCore");
        }

        #endregion
    }
}
